{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "36224718",
   "metadata": {
    "origin_pos": 0
   },
   "source": [
    "# 从全连接层到卷积\n",
    ":label:`sec_why-conv`\n",
    "\n",
    "我们之前讨论的多层感知机十分适合处理表格数据，其中行对应样本，列对应特征。\n",
    "对于表格数据，我们寻找的模式可能涉及特征之间的交互，但是我们不能预先假设任何与特征交互相关的先验结构。\n",
    "此时，多层感知机可能是最好的选择，然而对于高维感知数据，这种缺少结构的网络可能会变得不实用。\n",
    "\n",
    "例如，在之前猫狗分类的例子中：假设我们有一个足够充分的照片数据集，数据集中是拥有标注的照片，每张照片具有百万级像素，这意味着网络的每次输入都有一百万个维度。\n",
    "即使将隐藏层维度降低到1000，这个全连接层也将有$10^6 \\times 10^3 = 10^9$个参数。\n",
    "想要训练这个模型将不可实现，因为需要有大量的GPU、分布式优化训练的经验和超乎常人的耐心。\n",
    "\n",
    "有些读者可能会反对这个观点，认为要求百万像素的分辨率可能不是必要的。\n",
    "然而，即使分辨率减小为十万像素，使用1000个隐藏单元的隐藏层也可能不足以学习到良好的图像特征，在真实的系统中我们仍然需要数十亿个参数。\n",
    "此外，拟合如此多的参数还需要收集大量的数据。\n",
    "然而，如今人类和机器都能很好地区分猫和狗：这是因为图像中本就拥有丰富的结构，而这些结构可以被人类和机器学习模型使用。\n",
    "*卷积神经网络*（convolutional neural networks，CNN）是机器学习利用自然图像中一些已知结构的创造性方法。\n",
    "\n",
    "## 不变性\n",
    "\n",
    "想象一下，假设我们想从一张图片中找到某个物体。\n",
    "合理的假设是：无论哪种方法找到这个物体，都应该和物体的位置无关。\n",
    "理想情况下，我们的系统应该能够利用常识：猪通常不在天上飞，飞机通常不在水里游泳。\n",
    "但是，如果一只猪出现在图片顶部，我们还是应该认出它。\n",
    "我们可以从儿童游戏”沃尔多在哪里”（ :numref:`img_waldo`）中得到灵感：\n",
    "在这个游戏中包含了许多充斥着活动的混乱场景，而沃尔多通常潜伏在一些不太可能的位置，读者的目标就是找出他。\n",
    "尽管沃尔多的装扮很有特点，但是在眼花缭乱的场景中找到他也如大海捞针。\n",
    "然而沃尔多的样子并不取决于他潜藏的地方，因此我们可以使用一个“沃尔多检测器”扫描图像。\n",
    "该检测器将图像分割成多个区域，并为每个区域包含沃尔多的可能性打分。\n",
    "卷积神经网络正是将*空间不变性*（spatial invariance）的这一概念系统化，从而基于这个模型使用较少的参数来学习有用的表示。\n",
    "\n",
    "![沃尔多游戏示例图。](../img/where-wally-walker-books.jpg)\n",
    ":width:`400px`\n",
    ":label:`img_waldo`\n",
    "\n",
    "现在，我们将上述想法总结一下，从而帮助我们设计适合于计算机视觉的神经网络架构。\n",
    "\n",
    "1. *平移不变性*（translation invariance）：不管检测对象出现在图像中的哪个位置，神经网络的前面几层应该对相同的图像区域具有相似的反应，即为“平移不变性”。\n",
    "1. *局部性*（locality）：神经网络的前面几层应该只探索输入图像中的局部区域，而不过度在意图像中相隔较远区域的关系，这就是“局部性”原则。最终，可以聚合这些局部特征，以在整个图像级别进行预测。\n",
    "\n",
    "让我们看看这些原则是如何转化为数学表示的。\n",
    "\n",
    "## 多层感知机的限制\n",
    "\n",
    "首先，多层感知机的输入是二维图像$\\mathbf{X}$，其隐藏表示$\\mathbf{H}$在数学上是一个矩阵，在代码中表示为二维张量。\n",
    "其中$\\mathbf{X}$和$\\mathbf{H}$具有相同的形状。\n",
    "为了方便理解，我们可以认为，无论是输入还是隐藏表示都拥有空间结构。\n",
    "\n",
    "使用$[\\mathbf{X}]_{i, j}$和$[\\mathbf{H}]_{i, j}$分别表示输入图像和隐藏表示中位置（$i$,$j$）处的像素。\n",
    "为了使每个隐藏神经元都能接收到每个输入像素的信息，我们将参数从权重矩阵（如同我们先前在多层感知机中所做的那样）替换为四阶权重张量$\\mathsf{W}$。假设$\\mathbf{U}$包含偏置参数，我们可以将全连接层形式化地表示为\n",
    "\n",
    "$$\\begin{aligned} \\left[\\mathbf{H}\\right]_{i, j} &= [\\mathbf{U}]_{i, j} + \\sum_k \\sum_l[\\mathsf{W}]_{i, j, k, l}  [\\mathbf{X}]_{k, l}\\\\ &=  [\\mathbf{U}]_{i, j} +\n",
    "\\sum_a \\sum_b [\\mathsf{V}]_{i, j, a, b}  [\\mathbf{X}]_{i+a, j+b}.\\end{aligned}$$\n",
    "\n",
    "其中，从$\\mathsf{W}$到$\\mathsf{V}$的转换只是形式上的转换，因为在这两个四阶张量的元素之间存在一一对应的关系。\n",
    "我们只需重新索引下标$(k, l)$，使$k = i+a$、$l = j+b$，由此可得$[\\mathsf{V}]_{i, j, a, b} = [\\mathsf{W}]_{i, j, i+a, j+b}$。\n",
    "索引$a$和$b$通过在正偏移和负偏移之间移动覆盖了整个图像。\n",
    "对于隐藏表示中任意给定位置（$i$,$j$）处的像素值$[\\mathbf{H}]_{i, j}$，可以通过在$x$中以$(i, j)$为中心对像素进行加权求和得到，加权使用的权重为$[\\mathsf{V}]_{i, j, a, b}$。\n",
    "\n",
    "### 平移不变性\n",
    "\n",
    "现在引用上述的第一个原则：平移不变性。\n",
    "这意味着检测对象在输入$\\mathbf{X}$中的平移，应该仅导致隐藏表示$\\mathbf{H}$中的平移。也就是说，$\\mathsf{V}$和$\\mathbf{U}$实际上不依赖于$(i, j)$的值，即$[\\mathsf{V}]_{i, j, a, b} = [\\mathbf{V}]_{a, b}$。并且$\\mathbf{U}$是一个常数，比如$u$。因此，我们可以简化$\\mathbf{H}$定义为：\n",
    "\n",
    "$$[\\mathbf{H}]_{i, j} = u + \\sum_a\\sum_b [\\mathbf{V}]_{a, b} [\\mathbf{X}]_{i+a, j+b}.$$\n",
    "\n",
    "这就是*卷积*（convolution）。我们是在使用系数$[\\mathbf{V}]_{a, b}$对位置$(i, j)$附近的像素$(i+a, j+b)$进行加权得到$[\\mathbf{H}]_{i, j}$。\n",
    "注意，$[\\mathbf{V}]_{a, b}$的系数比$[\\mathsf{V}]_{i, j, a, b}$少很多，因为前者不再依赖于图像中的位置。这就是显著的进步！\n",
    "\n",
    "### 局部性\n",
    "\n",
    "现在引用上述的第二个原则：局部性。如上所述，为了收集用来训练参数$[\\mathbf{H}]_{i, j}$的相关信息，我们不应偏离到距$(i, j)$很远的地方。这意味着在$|a|> \\Delta$或$|b| > \\Delta$的范围之外，我们可以设置$[\\mathbf{V}]_{a, b} = 0$。因此，我们可以将$[\\mathbf{H}]_{i, j}$重写为\n",
    "\n",
    "$$[\\mathbf{H}]_{i, j} = u + \\sum_{a = -\\Delta}^{\\Delta} \\sum_{b = -\\Delta}^{\\Delta} [\\mathbf{V}]_{a, b}  [\\mathbf{X}]_{i+a, j+b}.$$\n",
    ":eqlabel:`eq_conv-layer`\n",
    "\n",
    "简而言之， :eqref:`eq_conv-layer`是一个*卷积层*（convolutional layer），而卷积神经网络是包含卷积层的一类特殊的神经网络。\n",
    "在深度学习研究社区中，$\\mathbf{V}$被称为*卷积核*（convolution kernel）或者*滤波器*（filter），亦或简单地称之为该卷积层的*权重*，通常该权重是可学习的参数。\n",
    "当图像处理的局部区域很小时，卷积神经网络与多层感知机的训练差异可能是巨大的：以前，多层感知机可能需要数十亿个参数来表示网络中的一层，而现在卷积神经网络通常只需要几百个参数，而且不需要改变输入或隐藏表示的维数。\n",
    "参数大幅减少的代价是，我们的特征现在是平移不变的，并且当确定每个隐藏活性值时，每一层只包含局部的信息。\n",
    "以上所有的权重学习都将依赖于归纳偏置。当这种偏置与现实相符时，我们就能得到样本有效的模型，并且这些模型能很好地泛化到未知数据中。\n",
    "但如果这偏置与现实不符时，比如当图像不满足平移不变时，我们的模型可能难以拟合我们的训练数据。\n",
    "\n",
    "## 卷积\n",
    "\n",
    "在进一步讨论之前，我们先简要回顾一下为什么上面的操作被称为卷积。在数学中，两个函数（比如$f, g: \\mathbb{R}^d \\to \\mathbb{R}$）之间的“卷积”被定义为\n",
    "\n",
    "$$(f * g)(\\mathbf{x}) = \\int f(\\mathbf{z}) g(\\mathbf{x}-\\mathbf{z}) d\\mathbf{z}.$$\n",
    "\n",
    "也就是说，卷积是当把一个函数“翻转”并移位$\\mathbf{x}$时，测量$f$和$g$之间的重叠。\n",
    "当为离散对象时，积分就变成求和。例如，对于由索引为$\\mathbb{Z}$的、平方可和的、无限维向量集合中抽取的向量，我们得到以下定义：\n",
    "\n",
    "$$(f * g)(i) = \\sum_a f(a) g(i-a).$$\n",
    "\n",
    "对于二维张量，则为$f$的索引$(a, b)$和$g$的索引$(i-a, j-b)$上的对应加和：\n",
    "\n",
    "$$(f * g)(i, j) = \\sum_a\\sum_b f(a, b) g(i-a, j-b).$$\n",
    ":eqlabel:`eq_2d-conv-discrete`\n",
    "\n",
    "这看起来类似于 :eqref:`eq_conv-layer`，但有一个主要区别：这里不是使用$(i+a, j+b)$，而是使用差值。然而，这种区别是表面的，因为我们总是可以匹配 :eqref:`eq_conv-layer`和 :eqref:`eq_2d-conv-discrete`之间的符号。我们在 :eqref:`eq_conv-layer`中的原始定义更正确地描述了*互相关*（cross-correlation），这个问题将在下一节中讨论。\n",
    "\n",
    "## “沃尔多在哪里”回顾\n",
    "\n",
    "回到上面的“沃尔多在哪里”游戏，让我们看看它到底是什么样子。卷积层根据滤波器$\\mathbf{V}$选取给定大小的窗口，并加权处理图片，如 :numref:`fig_waldo_mask`中所示。我们的目标是学习一个模型，以便探测出在“沃尔多”最可能出现的地方。\n",
    "\n",
    "![发现沃尔多。](../img/waldo-mask.jpg)\n",
    ":width:`400px`\n",
    ":label:`fig_waldo_mask`\n",
    "\n",
    "### 通道\n",
    ":label:`subsec_why-conv-channels`\n",
    "\n",
    "然而这种方法有一个问题：我们忽略了图像一般包含三个通道/三种原色（红色、绿色和蓝色）。\n",
    "实际上，图像不是二维张量，而是一个由高度、宽度和颜色组成的三维张量，比如包含$1024 \\times 1024 \\times 3$个像素。\n",
    "前两个轴与像素的空间位置有关，而第三个轴可以看作每个像素的多维表示。\n",
    "因此，我们将$\\mathsf{X}$索引为$[\\mathsf{X}]_{i, j, k}$。由此卷积相应地调整为$[\\mathsf{V}]_{a,b,c}$，而不是$[\\mathbf{V}]_{a,b}$。\n",
    "\n",
    "此外，由于输入图像是三维的，我们的隐藏表示$\\mathsf{H}$也最好采用三维张量。\n",
    "换句话说，对于每一个空间位置，我们想要采用一组而不是一个隐藏表示。这样一组隐藏表示可以想象成一些互相堆叠的二维网格。\n",
    "因此，我们可以把隐藏表示想象为一系列具有二维张量的*通道*（channel）。\n",
    "这些通道有时也被称为*特征映射*（feature maps），因为每个通道都向后续层提供一组空间化的学习特征。\n",
    "直观上可以想象在靠近输入的底层，一些通道专门识别边缘，而一些通道专门识别纹理。\n",
    "\n",
    "为了支持输入$\\mathsf{X}$和隐藏表示$\\mathsf{H}$中的多个通道，我们可以在$\\mathsf{V}$中添加第四个坐标，即$[\\mathsf{V}]_{a, b, c, d}$。综上所述，\n",
    "\n",
    "$$[\\mathsf{H}]_{i,j,d} = \\sum_{a = -\\Delta}^{\\Delta} \\sum_{b = -\\Delta}^{\\Delta} \\sum_c [\\mathsf{V}]_{a, b, c, d} [\\mathsf{X}]_{i+a, j+b, c},$$\n",
    ":eqlabel:`eq_conv-layer-channels`\n",
    "\n",
    "其中隐藏表示$\\mathsf{H}$中的索引$d$表示输出通道，而随后的输出将继续以三维张量$\\mathsf{H}$作为输入进入下一个卷积层。\n",
    "所以， :eqref:`eq_conv-layer-channels`可以定义具有多个通道的卷积层，而其中$\\mathsf{V}$是该卷积层的权重。\n",
    "\n",
    "然而，仍有许多问题亟待解决。\n",
    "例如，图像中是否到处都有存在沃尔多的可能？如何有效地计算输出层？如何选择适当的激活函数？为了训练有效的网络，如何做出合理的网络设计选择？我们将在本章的其它部分讨论这些问题。\n",
    "\n",
    "## 小结\n",
    "\n",
    "- 图像的平移不变性使我们以相同的方式处理局部图像，而不在乎它的位置。\n",
    "- 局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。\n",
    "- 在图像处理中，卷积层通常比全连接层需要更少的参数，但依旧获得高效用的模型。\n",
    "- 卷积神经网络（CNN）是一类特殊的神经网络，它可以包含多个卷积层。\n",
    "- 多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。\n",
    "\n",
    "## 练习\n",
    "\n",
    "1. 假设卷积层 :eqref:`eq_conv-layer`覆盖的局部区域$\\Delta = 0$。在这种情况下，证明卷积内核为每组通道独立地实现一个全连接层。\n",
    "1. 为什么平移不变性可能也不是好主意呢？\n",
    "1. 当从图像边界像素获取隐藏表示时，我们需要思考哪些问题？\n",
    "1. 描述一个类似的音频卷积层的架构。\n",
    "1. 卷积层也适合于文本数据吗？为什么？\n",
    "1. 证明在 :eqref:`eq_2d-conv-discrete`中，$f * g = g * f$。\n",
    "\n",
    "[Discussions](https://discuss.d2l.ai/t/5767)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_pytorch_p36",
   "name": "conda_pytorch_p36"
  },
  "language_info": {
   "name": "python"
  },
  "required_libs": []
 },
 "nbformat": 4,
 "nbformat_minor": 5
}